﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializableObjectDictionary.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the SerializableObjectDictionary type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Utils
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Data;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;

    /// <summary>
    /// The serializable object dictionary.
    /// </summary>
    /// <typeparam name="TKey">
    /// </typeparam>
    [Serializable]
    [XmlRoot("Dictionary")]
    public class SerializableObjectDictionary<TKey> : Dictionary<TKey, object>, IXmlSerializable
    {
        #region Public Methods
        public SerializableObjectDictionary()
        {}

        public SerializableObjectDictionary(HybridDictionary dictionary)
        {
            if (dictionary != null)
            {
                foreach (TKey dictionaryName in dictionary.Keys)
                {
                    this.Add(dictionaryName, dictionary[dictionaryName]);
                }
            }
        }

        /// <summary>
        /// The add.
        /// </summary>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <param name="serializableValue">
        /// The serializableValue.
        /// </param>
        public new void Add(TKey key, object serializableValue)
        {
            ValidationHelper.VerifyObjectArgument(serializableValue, "serializableValue");

            if (!serializableValue.GetType().IsSerializable)
            {
                throw new ArgumentException(
                    "Attempt to add a non serializable object of type" + serializableValue.GetType() +
                    "to a SerializableObjectDictionary instance", 
                    "value");
            }

            base.Add(key, serializableValue);
        }

        /// <summary>
        /// The add.
        /// </summary>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="overwrite">
        /// The overwrite.
        /// </param>
        public void Add(TKey key, object value, bool overwrite)
        {
            if (!this.ContainsKey(key))
            {
                this.Add(key, value);
            }
            else
            {
                if (overwrite)
                {
                    this.Remove(key);
                    this.Add(key, value);
                }
                else
                {
                    string message = "Not Adding Variable (as it exists), key : " + key + ", value : " + value;

                    throw new DuplicateNameException(message);
                }
            }
        }

        /// <summary>
        /// Gets a value for a given key. It throws an exception if the value if the type espcified by T does not correspond to the 
        /// stored type. returns the dedault value for the type if key has not been found.
        /// </summary>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <typeparam name="T">
        /// The type of the value to be returned
        /// </typeparam>
        /// <returns>
        /// </returns>
        public T Get<T>(TKey key)
        {
            if (this.ContainsKey(key))
            {
                object valueObj = this[key];
                return (T)valueObj;
            }
            else
            {
                return default(T);
            }
        }

        /// <summary>
        /// Gets the value for a given key as a string.
        /// </summary>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <returns>
        /// The get as string.
        /// </returns>
        public string GetAsString(TKey key)
        {
            object valueObj = this[key];

            if (valueObj != null)
            {
                return valueObj.ToString();
            }

            return null;
        }

        #endregion

        #region Implemented Interfaces

        #region IXmlSerializable

        /// <summary>
        /// This property is reserved, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"></see> to the class instead.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"></see> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"></see> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"></see> method.
        /// </returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Generates a collection from its XML representation.
        /// </summary>
        /// <param name="reader">
        /// The <see cref="T:System.Xml.XmlReader"></see> stream from which the object is deserialized.
        /// </param>
        public void ReadXml(XmlReader reader)
        {
            try
            {
                // Remove existing data from the collection.
                this.Clear();

                bool wasEmpty = reader.IsEmptyElement;
                reader.Read();

                if (wasEmpty)
                {
                    return;
                }

                var keySerializer = new XmlSerializer(typeof(TKey));

                while (reader.NodeType != XmlNodeType.EndElement)
                {
                    // deserialise the type name
                    // string typeName = reader.GetAttribute("type");

                    // reader.ReadStartElement("item");
                    reader.MoveToContent();

                    // deserialise the type name
                    string typeName = reader.GetAttribute("type");
                    reader.ReadStartElement("item");

                    // deserialise the key
                    reader.ReadStartElement("key");
                    var key = (TKey)keySerializer.Deserialize(reader);
                    reader.ReadEndElement();

                    // deserialise the value
                    reader.ReadStartElement("value");

                    object value = new XmlSerializer(Type.GetType(typeName)).Deserialize(reader);
                    reader.ReadEndElement();

                    this.Add(key, value);

                    reader.ReadEndElement();
                    reader.MoveToContent();
                }

                reader.ReadEndElement();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        /// <summary>
        /// Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">
        /// The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized.
        /// </param>
        public void WriteXml(XmlWriter writer)
        {
            var keySerializer = new XmlSerializer(typeof(TKey));

            foreach (TKey key in this.Keys)
            {
                // start the item and write the type attribute
                writer.WriteStartElement("item");
                writer.WriteAttributeString("type", this[key].GetType().AssemblyQualifiedName);

                // write the key value
                writer.WriteStartElement("key");
                keySerializer.Serialize(writer, key);
                writer.WriteEndElement();

                // write the value
                writer.WriteStartElement("value");
                new XmlSerializer(this[key].GetType()).Serialize(writer, this[key]);
                writer.WriteEndElement();

                // close item element
                writer.WriteEndElement();
            }
        }

        #endregion

        #endregion

        /*
        public NameValueCollection ToNameValueCollection()
        {
            NameValueCollection nvc = new NameValueCollection();
            foreach (TKey key in this.Keys)
            {
                nvc.Add(key ,this[key]);
            }

            return nvc;
        }
        */
    }
}